﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LitJson;

[AddComponentMenu("PUEX/Render/SpriteRender")]
public class PUEXSpriteRender : PUEXRender {

	public enum SpriteType
	{
		ST_PLANE_XY,
		ST_PLANE_XZ,
		ST_PLANE_YZ,
		ST_BILLBOARD_FACE_CAMERA,
		ST_BILLBOARD_FACE_DIRECTION,
	};
	
	#region - Editable property
	[Header("SpriteRender:")]
	public SpriteType spriteType;
	public bool lockRotateXAxis;
	public bool lockRotateYAxis;
	public bool lockRotateZAxis;
	#endregion
	
	#region - Property
	protected Mesh _renderMesh;
	#endregion
	
	#region - Lifecycle
	public PUEXSpriteRender () : base()
	{
		spriteType = SpriteType.ST_BILLBOARD_FACE_CAMERA;
		lockRotateXAxis = false;
		lockRotateYAxis = false;
		lockRotateZAxis = false;
	}

	public void Awake ()
	{
		switch (blendType) {
		case BlendType.BT_NONE: {
			_material = new Material (Shader.Find ("PUEX/particle_trans_alpha"));
			_material.mainTexture = texture;
			break;
		}
		case BlendType.BT_NORMAL: {
			_material = new Material (Shader.Find ("PUEX/particle_trans_alpha"));
			_material.mainTexture = texture;
			break;
		}
		case BlendType.BT_ADD: {
			_material = new Material (Shader.Find ("PUEX/particle_trans_add"));
			_material.mainTexture = texture;
			break;
		}
		case BlendType.BT_MULTIPLY: {
			_material = new Material (Shader.Find ("PUEX/particle_trans_multiply"));
			_material.mainTexture = texture;
			break;
		}
		}

		_renderMesh = new Mesh ();
		_renderMesh.MarkDynamic ();
	}
	#endregion

	#region - Draw
	protected Matrix4x4 getTransformForRender (PUEXParticle particle) 
	{
		Quaternion orientation = particle.orientation;

		if (lockRotateXAxis || lockRotateYAxis || lockRotateZAxis) {
			Vector3 eAngles = orientation.eulerAngles;
			if (lockRotateXAxis)
				eAngles.x = 0;
			if (lockRotateYAxis)
				eAngles.y = 0;
			if (lockRotateZAxis)
				eAngles.z = 0;
			orientation = Quaternion.Euler(eAngles);
		}
		
		return Matrix4x4.TRS (particle.position, orientation, particle.dimensions());
	}

	public override void visit ()
	{
		base.visit ();

		_renderMesh.Clear ();
		
		Vector3[] vertices = new Vector3[ownerEmitter.aliveParticleCount * 4];
		Vector2[] uvs = new Vector2[ownerEmitter.aliveParticleCount * 4];
		Color[] colors = new Color[ownerEmitter.aliveParticleCount * 4];
		int[] triangles = new int[ownerEmitter.aliveParticleCount * 6];

		if (spriteType == SpriteType.ST_PLANE_XY || spriteType == SpriteType.ST_PLANE_XZ || spriteType == SpriteType.ST_PLANE_YZ) {
			int index = 0;
			foreach (var particle in ownerEmitter.particlePool) {
				if (particle.alive) {
					Matrix4x4 transform = getTransformForRender (particle);
					int index4 = index * 4;

					switch (spriteType) {
					case SpriteType.ST_PLANE_XY:
						{
							vertices [index4] = transform.MultiplyPoint (new Vector3 (-0.5f, -0.5f, +0.0f));
							vertices [index4 + 1] = transform.MultiplyPoint (new Vector3 (-0.5f, +0.5f, +0.0f));
							vertices [index4 + 2] = transform.MultiplyPoint (new Vector3 (+0.5f, +0.5f, +0.0f));
							vertices [index4 + 3] = transform.MultiplyPoint (new Vector3 (+0.5f, -0.5f, +0.0f));

							break;
						}
					case SpriteType.ST_PLANE_XZ:
						{
							vertices [index4] = transform.MultiplyPoint (new Vector3 (-0.5f, +0.0f, -0.5f));
							vertices [index4 + 1] = transform.MultiplyPoint (new Vector3 (-0.5f, +0.0f, +0.5f));
							vertices [index4 + 2] = transform.MultiplyPoint (new Vector3 (+0.5f, +0.0f, +0.5f));
							vertices [index4 + 3] = transform.MultiplyPoint (new Vector3 (+0.5f, +0.0f, -0.5f));

							break;
						}
					case SpriteType.ST_PLANE_YZ:
						{
							vertices [index4] = transform.MultiplyPoint (new Vector3 (+0.0f, -0.5f, -0.5f));
							vertices [index4 + 1] = transform.MultiplyPoint (new Vector3 (+0.0f, -0.5f, +0.5f));
							vertices [index4 + 2] = transform.MultiplyPoint (new Vector3 (+0.0f, +0.5f, +0.5f));
							vertices [index4 + 3] = transform.MultiplyPoint (new Vector3 (+0.0f, +0.5f, -0.5f));

							break;
						}
					default:
						break;
					}
					
					uvs [index4] = Vector2.Scale (particle.uvOffset, particle.uvScale);
					uvs [index4 + 1] = Vector2.Scale (particle.uvOffset + new Vector2 (0f, 1f), particle.uvScale);
					uvs [index4 + 2] = Vector2.Scale (particle.uvOffset + new Vector2 (1f, 1f), particle.uvScale);
					uvs [index4 + 3] = Vector2.Scale (particle.uvOffset + new Vector2 (1f, 0f), particle.uvScale);
					
					colors [index4] = particle.color;
					colors [index4 + 1] = particle.color;
					colors [index4 + 2] = particle.color;
					colors [index4 + 3] = particle.color;
					
					triangles [index * 6] = index4;
					triangles [index * 6 + 1] = index4 + 1;
					triangles [index * 6 + 2] = index4 + 2;
					
					triangles [index * 6 + 3] = index4 + 2;
					triangles [index * 6 + 4] = index4 + 3;
					triangles [index * 6 + 5] = index4 + 0;
					
					index++;
				}
			}

		} else if (spriteType == SpriteType.ST_BILLBOARD_FACE_CAMERA) {
			Vector3 cameraForward;
			if (ownerEmitter.keepLocal) {
				Matrix4x4 invMat = ownerEmitter.ownerParticleSystem.systemToWorldTransform.inverse;
				cameraForward = invMat.MultiplyVector(Camera.current.transform.forward);
			} else {
				cameraForward = Camera.current.transform.forward;
			}

			int index = 0;
			foreach (var particle in ownerEmitter.particlePool) {
				if (particle.alive) {
					Quaternion orientation = Quaternion.LookRotation (cameraForward, Vector3.up) * Quaternion.AngleAxis (particle.rotation, Vector3.forward);
					Matrix4x4 transform =  Matrix4x4.TRS (particle.position, orientation, particle.dimensions());
					int index4 = index * 4;

					vertices [index4] = transform.MultiplyPoint (new Vector3 (-0.5f, -0.5f, +0.0f));
					vertices [index4 + 1] = transform.MultiplyPoint (new Vector3 (-0.5f, +0.5f, +0.0f));
					vertices [index4 + 2] = transform.MultiplyPoint (new Vector3 (+0.5f, +0.5f, +0.0f));
					vertices [index4 + 3] = transform.MultiplyPoint (new Vector3 (+0.5f, -0.5f, +0.0f));
					
					uvs [index4] = Vector2.Scale (particle.uvOffset, particle.uvScale);
					uvs [index4 + 1] = Vector2.Scale (particle.uvOffset + new Vector2 (0f, 1f), particle.uvScale);
					uvs [index4 + 2] = Vector2.Scale (particle.uvOffset + new Vector2 (1f, 1f), particle.uvScale);
					uvs [index4 + 3] = Vector2.Scale (particle.uvOffset + new Vector2 (1f, 0f), particle.uvScale);
					
					colors [index4] = particle.color;
					colors [index4 + 1] = particle.color;
					colors [index4 + 2] = particle.color;
					colors [index4 + 3] = particle.color;
					
					triangles [index * 6] = index4;
					triangles [index * 6 + 1] = index4 + 1;
					triangles [index * 6 + 2] = index4 + 2;
					
					triangles [index * 6 + 3] = index4 + 2;
					triangles [index * 6 + 4] = index4 + 3;
					triangles [index * 6 + 5] = index4 + 0;
					
					index++;
				}
			}

		} else if (spriteType == SpriteType.ST_BILLBOARD_FACE_DIRECTION) {
			Matrix4x4 cameraMat;
			if (ownerEmitter.keepLocal) {
				Matrix4x4 invMat = ownerEmitter.ownerParticleSystem.systemToWorldTransform.inverse;
				cameraMat = invMat * Camera.current.transform.localToWorldMatrix;
			} else {
				cameraMat = Camera.current.transform.localToWorldMatrix;
			}

			Vector3 right = new Vector3(cameraMat[0], cameraMat[1], cameraMat[2]);
			Vector3 up = new Vector3(cameraMat[4], cameraMat[5], cameraMat[6]);
			Vector3 backward = new Vector3(cameraMat[8], cameraMat[9], cameraMat[10]);

			int index = 0;
			foreach (var particle in ownerEmitter.particlePool) {
				if (particle.alive) {
					up = particle.direction;
					right = Vector3.Cross(up, backward);
					right.Normalize();

					Vector3 dimensions = particle.dimensions();
					Vector3 halfwidth = right * (dimensions.x * 0.5f);
					Vector3 halfheight = up * (dimensions.y * 0.5f);

					int index4 = index * 4;
					
					vertices [index4] = particle.position + (-halfwidth - halfheight);
					vertices [index4 + 1] = particle.position + (-halfwidth + halfheight);
					vertices [index4 + 2] = particle.position + (halfwidth + halfheight);
					vertices [index4 + 3] = particle.position + (halfwidth - halfheight);

					Vector2 lb_uv = Vector2.Scale (particle.uvOffset, particle.uvScale);
					Vector2 rt_uv = Vector2.Scale (particle.uvOffset + new Vector2 (1f, 1f), particle.uvScale);
					float costheta = Mathf.Cos(-particle.rotation * Mathf.Deg2Rad);
					float sintheta = Mathf.Sin(-particle.rotation * Mathf.Deg2Rad);
					Vector2 texOffset = lb_uv + (rt_uv - lb_uv) * 0.5f;
					Vector2 val;

					val = new Vector2((lb_uv.x - texOffset.x), (lb_uv.y - texOffset.y));
					val = new Vector2(val.x * costheta - val.y * sintheta, val.x * sintheta + val.y * costheta);
					uvs [index4] = new Vector2(val.x + texOffset.x, val.y + texOffset.y);

					val = new Vector2((lb_uv.x - texOffset.x), (rt_uv.y - texOffset.y));
					val = new Vector2(val.x * costheta - val.y * sintheta, val.x * sintheta + val.y * costheta);
					uvs [index4 + 1] = new Vector2(val.x + texOffset.x, val.y + texOffset.y);
					
					val = new Vector2((rt_uv.x - texOffset.x), (rt_uv.y - texOffset.y));
					val = new Vector2(val.x * costheta - val.y * sintheta, val.x * sintheta + val.y * costheta);
					uvs [index4 + 2] = new Vector2(val.x + texOffset.x, val.y + texOffset.y);
					
					val = new Vector2((rt_uv.x - texOffset.x), (lb_uv.y - texOffset.y));
					val = new Vector2(val.x * costheta - val.y * sintheta, val.x * sintheta + val.y * costheta);
					uvs [index4 + 3] = new Vector2(val.x + texOffset.x, val.y + texOffset.y);
					
					colors [index4] = particle.color;
					colors [index4 + 1] = particle.color;
					colors [index4 + 2] = particle.color;
					colors [index4 + 3] = particle.color;
					
					triangles [index * 6] = index4;
					triangles [index * 6 + 1] = index4 + 1;
					triangles [index * 6 + 2] = index4 + 2;
					
					triangles [index * 6 + 3] = index4 + 2;
					triangles [index * 6 + 4] = index4 + 3;
					triangles [index * 6 + 5] = index4 + 0;
					
					index++;
				}
			}
		}
		
		_renderMesh.vertices = vertices;
		_renderMesh.uv = uvs;
		_renderMesh.colors = colors;
		_renderMesh.triangles = triangles;
	}

	public override void draw ()
	{
		for (int i = 0; i < _material.passCount; i++) {
			_material.SetPass(i);
			
			if (ownerEmitter.keepLocal) {
				Graphics.DrawMeshNow(_renderMesh, ownerEmitter.ownerParticleSystem.systemToWorldTransform);
			} else {
				Graphics.DrawMeshNow(_renderMesh, Matrix4x4.identity);
			}
		}
	}
	#endregion

	#region - Serialization
	public override JsonData save (JsonData emitterData, JsonData systemData)
	{
		JsonData ret = base.save (emitterData, systemData);

		if (guiFX) {
			switch (blendType) {
			case BlendType.BT_NONE: {
				ret ["material"] = "material/puex_ui_particle_trans_alpha.mat";
				break;
			}
			case BlendType.BT_NORMAL: {
				ret ["material"] = "material/puex_ui_particle_trans_alpha.mat";
				break;
			}
			case BlendType.BT_ADD: {
				ret ["material"] = "material/puex_ui_particle_trans_add.mat";
				break;
			}
			case BlendType.BT_MULTIPLY: {
				ret ["material"] = "material/puex_ui_particle_trans_multiply.mat";
				break;
			}
			}
		} else {
			switch (blendType) {
			case BlendType.BT_NONE: {
				ret ["material"] = "material/puex_particle_trans_alpha.mat";
				break;
			}
			case BlendType.BT_NORMAL: {
				ret ["material"] = "material/puex_particle_trans_alpha.mat";
				break;
			}
			case BlendType.BT_ADD: {
				ret ["material"] = "material/puex_particle_trans_add.mat";
				break;
			}
			case BlendType.BT_MULTIPLY: {
				ret ["material"] = "material/puex_particle_trans_multiply.mat";
				break;
			}
			}
		}

		switch (spriteType) {
		case SpriteType.ST_PLANE_XY: {
			ret ["sprite_type"] = "plane_xy";
			break;
		}
		case SpriteType.ST_PLANE_XZ: {
			ret ["sprite_type"] = "plane_xz";
			break;
		}
		case SpriteType.ST_PLANE_YZ: {
			ret ["sprite_type"] = "plane_yz";
			break;
		}
		case SpriteType.ST_BILLBOARD_FACE_CAMERA: {
			ret ["sprite_type"] = "billboard_face_camera";
			break;
		}
		case SpriteType.ST_BILLBOARD_FACE_DIRECTION: {
			ret ["sprite_type"] = "billboard_face_direction";
			break;
		}
		}

		ret ["lock_rotate_x_axis"] = lockRotateXAxis;
		ret ["lock_rotate_y_axis"] = lockRotateYAxis;
		ret ["lock_rotate_z_axis"] = lockRotateZAxis;
		
		return ret;
	}
	#endregion

}
